<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>OpenLayers WMTS 加载方式</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/ol@v7.4.0/ol.css">
    <script src="https://cdn.jsdelivr.net/npm/ol@v7.4.0/dist/ol.js"></script>
    <style>
        html,
        body {
            margin: 0;
            padding: 0;
            width: 100%;
            height: 100%;
            overflow: hidden;
        }

        .map {
            width: 100%;
            height: 100%;
        }

        .coordinates {
            position: absolute;
            bottom: 10px;
            right: 10px;
            background: rgba(255, 255, 255, 0.8);
            padding: 5px 10px;
            border-radius: 4px;
            font-size: 12px;
            z-index: 1000;
        }

        .layer-switcher {
            position: absolute;
            top: 10px;
            right: 10px;
            background: rgba(255, 255, 255, 0.9);
            padding: 10px;
            border-radius: 4px;
            z-index: 1000;
        }

        .layer-item {
            margin: 5px 0;
        }

        .config-btn {
            padding: 5px 10px;
            background: #409EFF;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 12px;
            margin-top: 10px;
        }

        .config-btn:hover {
            background: #66b1ff;
        }

        .config-panel {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: white;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 2px 12px rgba(0, 0, 0, 0.15);
            z-index: 1001;
            max-width: 80%;
            max-height: 80vh;
            display: none;
            overflow: auto;
        }

        .config-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 15px;
        }

        .config-header h3 {
            margin: 0;
            font-size: 16px;
        }

        .close-btn {
            background: none;
            border: none;
            font-size: 20px;
            cursor: pointer;
            color: #666;
        }

        .config-content {
            background: #f5f7fa;
            padding: 15px;
            border-radius: 4px;
            overflow: auto;
        }

        pre {
            margin: 0;
            white-space: pre-wrap;
            word-wrap: break-word;
            font-family: monospace;
            font-size: 12px;
        }

        .tabs {
            margin-bottom: 15px;
            border-bottom: 1px solid #ddd;
        }

        .tab-btn {
            padding: 8px 15px;
            border: none;
            background: none;
            cursor: pointer;
            margin-right: 10px;
            opacity: 0.7;
        }

        .tab-btn.active {
            opacity: 1;
            border-bottom: 2px solid #409EFF;
        }

        .tab-content {
            display: none;
        }

        .tab-content.active {
            display: block;
        }

        .compare-table {
            width: 100%;
            border-collapse: collapse;
            font-size: 12px;
        }

        .compare-table th,
        .compare-table td {
            padding: 8px;
            text-align: left;
            border-bottom: 1px solid #eee;
        }

        .compare-table th {
            background: #f5f7fa;
            font-weight: bold;
        }

        .compare-table tr:hover {
            background: #f5f7fa;
        }

        .diff {
            color: #f56c6c;
        }
    </style>
</head>

<body>
    <div id="map" class="map"></div>
    <div id="coordinates" class="coordinates"></div>
    <div class="layer-switcher">
        <div class="layer-item">
            <input type="radio" name="layer" id="tianditu" value="tianditu" checked>
            <label for="tianditu">天地图</label>
        </div>
        <div class="layer-item">
            <input type="radio" name="layer" id="gaode" value="gaode">
            <label for="gaode">高德地图</label>
        </div>
        <div class="layer-item">
            <button id="getConfigBtn" class="config-btn">获取WMTS配置</button>
        </div>
    </div>

    <div id="configPanel" class="config-panel">
        <div class="config-header">
            <h3>WMTS 配置信息</h3>
            <button class="close-btn">&times;</button>
        </div>
        <div class="config-content">
            <div class="tabs">
                <button class="tab-btn active" data-tab="config">配置信息</button>
                <button class="tab-btn" data-tab="compare">分辨率对比</button>
            </div>
            <div id="configContent" class="tab-content active"></div>
            <div id="compareContent" class="tab-content">
                <table class="compare-table">
                    <thead>
                        <tr>
                            <th>级别</th>
                            <th>标准值</th>
                            <th>计算值</th>
                            <th>XML值</th>
                        </tr>
                    </thead>
                    <tbody id="compareBody"></tbody>
                </table>
            </div>
        </div>
    </div>

    <script>
        /**
         * WMTS（Web Map Tile Service）是 OGC 制定的瓦片地图服务标准
         * 相比 XYZ 方式，WMTS 具有以下优势：
         * 1. 标准化服务接口，便于不同地图服务之间的互操作
         * 2. 支持多种坐标系统
         * 3. 支持多种图层样式
         * 4. 支持元数据查询
         * 5. 支持多种图片格式
         */

        // 天地图密钥
        const tdtKey = '8c88eba266a165eac9c085724708f2f9';

        /**
         * 创建天地图 WMTS 图层
         * 关键参数说明：
         * - url: WMTS 服务地址，{0-7}表示8个服务节点负载均衡
         * - layer: 图层名称，vec表示矢量底图
         * - format: 瓦片格式
         * - matrixSet: 瓦片矩阵集，w表示墨卡托投影
         * - style: 图层样式
         * - projection: 投影坐标系
         * - tileGrid: 瓦片网格定义
         *   - origin: 原点坐标
         *   - resolutions: 分辨率数组，从0级到17级
         *   - matrixIds: 矩阵ID数组，与分辨率一一对应
         */
        const tdtLayer = new ol.layer.Tile({
            source: new ol.source.WMTS({
                url: `http://t{0-7}.tianditu.gov.cn/vec_w/wmts?tk=${tdtKey}`,
                layer: 'vec',
                format: 'tiles',
                matrixSet: 'w',
                style: 'default',
                projection: 'EPSG:3857',
                tileGrid: new ol.tilegrid.WMTS({
                    origin: [-20037508.3427892, 20037508.3427892],
                    resolutions: [
                        156543.03392804097,  // 0级分辨率
                        78271.51696402048,   // 1级分辨率
                        39135.75848201024,   // 2级分辨率
                        19567.87924100512,
                        9783.93962050256,
                        4891.96981025128,
                        2445.98490512564,
                        1222.99245256282,
                        611.49622628141,
                        305.748113140705,
                        152.8740565703525,
                        76.43702828517625,
                        38.21851414258813,
                        19.109257071294063,
                        9.554628535647032,
                        4.777314267823516,
                        2.388657133911758,
                        1.194328566955879
                    ],
                    matrixIds: Array(18).fill(0).map((_, i) => i.toString())
                })
            }),
            visible: true
        });

        /**
         * 创建天地图标注图层
         * layer: cva 表示矢量注记图层
         * 其他参数同矢量底图
         */
        const resolutions = [];
        const matrixIds = [];
        const proj3857 = ol.proj.get('EPSG:3857');
        const maxResolution = ol.extent.getWidth(proj3857.getExtent()) / 256;

        const extent = proj3857.getExtent();
        // 范围的左上角就是原点坐标
        const origin = [extent[0], extent[3]];  // [minX, maxY]
        for (let i = 0; i < 18; i++) {
            resolutions[i] = maxResolution / Math.pow(2, i);
            matrixIds[i] = i.toString();
        }
        const tdtLabelLayer = new ol.layer.Tile({
            source: new ol.source.WMTS({
                url: `http://t{0-7}.tianditu.gov.cn/cva_w/wmts?tk=${tdtKey}`,
                layer: 'cva',
                format: 'tiles',
                matrixSet: 'w',
                style: 'default',
                projection: 'EPSG:3857',
                tileGrid: new ol.tilegrid.WMTS({
                    origin: origin,
                    resolutions: resolutions,
                    matrixIds: matrixIds
                })
            }),
            visible: true
        });

        /**
         * 创建高德地图图层（XYZ方式）
         * 用于对比 WMTS 和 XYZ 两种加载方式的区别
         */
        const gaodeLayer = new ol.layer.Tile({
            source: new ol.source.XYZ({
                url: 'https://webst0{1-4}.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}',
                crossOrigin: 'anonymous'
            }),
            visible: false
        });

        // 创建地图
        const map = new ol.Map({
            target: 'map',
            layers: [tdtLayer, tdtLabelLayer, gaodeLayer],
            view: new ol.View({
                center: ol.proj.fromLonLat([116.397428, 39.90923]),
                zoom: 12
            })
        });

        // 图层切换事件
        document.querySelectorAll('input[name="layer"]').forEach(radio => {
            radio.addEventListener('change', function (e) {
                const value = e.target.value;
                if (value === 'tianditu') {
                    tdtLayer.setVisible(true);
                    tdtLabelLayer.setVisible(true);
                    gaodeLayer.setVisible(false);
                } else if (value === 'gaode') {
                    tdtLayer.setVisible(false);
                    tdtLabelLayer.setVisible(false);
                    gaodeLayer.setVisible(true);
                }
            });
        });

        // 显示坐标
        map.on('pointermove', function (evt) {
            const coords = ol.proj.transform(evt.coordinate, 'EPSG:3857', 'EPSG:4326');
            document.getElementById('coordinates').innerHTML =
                `经度: ${coords[0].toFixed(6)}, 纬度: ${coords[1].toFixed(6)}`;
        });

        // 获取 WMTS 配置信息
        async function getWMTSConfig(layerType) {
            const wmtsUrl = `http://t0.tianditu.gov.cn/${layerType}_w/wmts?SERVICE=WMTS&REQUEST=GetCapabilities&VERSION=1.0.0&tk=${tdtKey}`;

            try {
                const response = await fetch(wmtsUrl);
                const text = await response.text();

                // 解析 XML
                const parser = new ol.format.WMTSCapabilities();
                const result = parser.read(text);

                // 获取配置
                const options = ol.source.WMTS.optionsFromCapabilities(result, {
                    layer: layerType,
                    matrixSet: 'w'
                });

                return {
                    origin: options.tileGrid.origins_[0],
                    resolutions: options.tileGrid.resolutions_,
                    matrixIds: options.tileGrid.matrixIds
                };
            } catch (error) {
                console.error('获取 WMTS 配置失败:', error);
                return null;
            }
        }

        // 标准分辨率值
        const standardResolutions = [
            156543.03392804097,
            78271.51696402048,
            39135.75848201024,
            19567.87924100512,
            9783.93962050256,
            4891.96981025128,
            2445.98490512564,
            1222.99245256282,
            611.49622628141,
            305.748113140705,
            152.8740565703525,
            76.43702828517625,
            38.21851414258813,
            19.109257071294063,
            9.554628535647032,
            4.777314267823516,
            2.388657133911758,
            1.194328566955879
        ];

        // 修改计算分辨率的部分
        const standardMaxResolution = 156543.03392804097;  // 标准最大分辨率

        // 计算分辨率值
        const calculatedResolutions = [];
        for (let i = 0; i < 18; i++) {
            calculatedResolutions[i] = standardMaxResolution / Math.pow(2, i);
        }

        // 修改转换函数
        function convertToStandardResolution(xmlValue, level) {
            // 第0级的 XML 值
            const xmlMaxResolution = 82832.21952727836;  // 从 XML 获取的第0级分辨率
            const standardMaxResolution = 156543.03392804097;  // 标准第0级分辨率

            // 使用比例关系进行转换
            return (xmlValue * standardMaxResolution) / xmlMaxResolution;
        }

        // 或者使用这种更简单的方式
        // function convertToStandardResolution(xmlValue, level) {
        //     // 转换系数 = 标准值 / XML值
        //     const conversionFactor = 1.889; // 156543.03392804097 / 82832.21952727836
        //     return xmlValue * conversionFactor;
        // }

        // 获取 EPSG:3857 的范围和原点信息
        function getProjectionInfo() {
            const proj3857 = ol.proj.get('EPSG:3857');
            const extent = proj3857.getExtent();
            
            // 范围的左上角就是原点坐标
            const origin = [extent[0], extent[3]];  // [minX, maxY]
            
            return {
                code: proj3857.getCode(),
                units: proj3857.getUnits(),
                extent: extent,
                origin: origin,
                // 计算宽度和高度
                width: ol.extent.getWidth(extent),
                height: ol.extent.getHeight(extent)
            };
        }

        // 修改显示分辨率对比的函数
        function showResolutionComparison(xmlResolutions) {
            const tbody = document.getElementById('compareBody');
            tbody.innerHTML = '';

            // 获取投影信息
            const projInfo = getProjectionInfo();

            // 添加表头说明
            const headerRow = document.createElement('tr');
            headerRow.innerHTML = `
                <td colspan="6" style="text-align: left; color: #666; font-size: 11px;">
                    <div style="margin-bottom: 10px;">
                        <strong>EPSG:3857 投影信息：</strong><br>
                        范围: [${projInfo.extent.map(v => v.toFixed(4)).join(', ')}]<br>
                        原点: [${projInfo.origin.map(v => v.toFixed(4)).join(', ')}]<br>
                        宽度: ${projInfo.width.toFixed(4)}<br>
                        高度: ${projInfo.height.toFixed(4)}<br>
                        单位: ${projInfo.units}
                    </div>
                    <div>
                        <strong>分辨率对比：</strong><br>
                        标准值：天地图官方文档提供的标准分辨率<br>
                        计算值：standardMaxResolution / Math.pow(2, level)<br>
                        XML值：从天地图 WMTS GetCapabilities 获取的值<br>
                        转换值：将 XML 值转换为标准分辨率
                    </div>
                </td>
            `;
            tbody.appendChild(headerRow);

            // 添加表头
            const tableHeader = document.createElement('tr');
            tableHeader.innerHTML = `
                <th>级别</th>
                <th>标准值</th>
                <th>计算值</th>
                <th>XML值</th>
                <th>转换值</th>
                <th>差异</th>
            `;
            tbody.appendChild(tableHeader);

            for (let i = 0; i < 18; i++) {
                const tr = document.createElement('tr');

                const standardValue = standardResolutions[i];
                const calculatedValue = standardMaxResolution / Math.pow(2, i);
                const xmlValue = xmlResolutions[i];
                const convertedValue = convertToStandardResolution(xmlValue, i);

                // 计算差异百分比
                const calcDiff = ((calculatedValue - standardValue) / standardValue * 100).toFixed(6);
                const convertDiff = ((convertedValue - standardValue) / standardValue * 100).toFixed(6);

                tr.innerHTML = `
                    <td>${i}</td>
                    <td>${standardValue.toFixed(8)}</td>
                    <td class="${Math.abs(calcDiff) > 0.0001 ? 'diff' : ''}">
                        ${calculatedValue.toFixed(8)}
                        <br><small>${calcDiff}%</small>
                    </td>
                    <td>${xmlValue.toFixed(8)}</td>
                    <td class="${Math.abs(convertDiff) > 0.0001 ? 'diff' : ''}">
                        ${convertedValue.toFixed(8)}
                        <br><small>${convertDiff}%</small>
                    </td>
                    <td>
                        计算值差异: ${calcDiff}%
                        <br>转换值差异: ${convertDiff}%
                    </td>
                `;
                tbody.appendChild(tr);
            }

            // 添加使用说明
            const noteRow = document.createElement('tr');
            noteRow.innerHTML = `
                <td colspan="6" style="text-align: left; color: #666; font-size: 11px;">
                    使用说明：<br>
                    1. 计算值 = standardMaxResolution / Math.pow(2, level)<br>
                    2. 转换值 = XML值 * (standardMaxResolution / xmlMaxResolution)<br>
                    3. standardMaxResolution = 156543.03392804097<br>
                    4. 差异百分比 = (当前值 - 标准值) / 标准值 * 100%
                </td>
            `;
            tbody.appendChild(noteRow);
        }

        // 修改按钮事件
        document.getElementById('getConfigBtn').addEventListener('click', async () => {
            const configPanel = document.getElementById('configPanel');
            const configContent = document.getElementById('configContent');

            configContent.textContent = '加载中...';
            configPanel.style.display = 'block';

            try {
                const vecConfig = await getWMTSConfig('vec');

                // 显示配置信息
                configContent.textContent = JSON.stringify(
                    {
                        vec: vecConfig
                    },
                    null,
                    2
                );

                // 显示分辨率对比
                showResolutionComparison(vecConfig.resolutions);
            } catch (error) {
                configContent.textContent = '获取配置失败: ' + error.message;
            }
        });

        // 标签切换事件
        document.querySelectorAll('.tab-btn').forEach(btn => {
            btn.addEventListener('click', function () {
                // 切换按钮状态
                document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
                this.classList.add('active');

                // 切换内容
                const tabId = this.dataset.tab;
                document.querySelectorAll('.tab-content').forEach(content => {
                    content.classList.remove('active');
                });
                document.getElementById(tabId + 'Content').classList.add('active');
            });
        });

        // 关闭按钮事件
        document.querySelector('.close-btn').addEventListener('click', () => {
            document.getElementById('configPanel').style.display = 'none';
        });
    </script>
</body>

</html>